#include "xsys_log.h"
#include "xallocator.h"
#include "xatexit_manager.h"

#include <cassert>
#include <cstdio>
#include <Windows.h>
#include <stdarg.h>
#include <ctime>
#include <new.h>

namespace xge{
namespace xge_internal{
#define XSYSLOG_HEAP_BUFFER_SIZE 4096
#define XSYSLOG_MAX_FORMAT_PASS 5
#define XSYSLOG_THREAD_LOCAL_BUFFER 1
#define XSYSLOG_HEAP_BUFFER 0
#define XSYSLOG_THREAD_LOCAL_UNUSED 2
  static FORCEINLINE void FatalAppOutput(
    char_t* msg
    ) {

#ifdef XGE_USE_WIDE_CHAR
      ::FatalAppExitW(0,
        msg
        );
#else
      ::FatalAppExitA(0,
        msg
        );
#endif
  }
  static const char_t* GetTimeString() {
    time_t timer = time(0);
    tm* time = localtime(&timer);
#ifdef XGE_USE_WIDE_CHAR
    return _wasctime(time);
#else 
    return asctime(time);
#endif

  }
#define XSYSLOG_INTERNAL_ERROR_HEADER  _xT("Fatal Error Occur In XGE Internal :")
namespace {
  class TinyLogFile {
  private:
    static size_t kTag ;
  public:
    // The name of log can be very trick
    // since we use the object array which can not initialize by passing the paremeter 
    // in the composition , we simply use an static index to do the initialization
    TinyLogFile() {
      String file_name(kLogFileNamePrefix[++kTag]);
      file_name += X_SYS_LOG_FILE_NAME; 
#ifdef XGE_USE_WIDE_CHAR
      file_handler_ = _wfopen( file_name.c_str() , _xT("wt+,ccs=UTF-8"));
#else
      file_handler_ = fopen( file_name.c_str() , "wt+" );
#endif
      // if the file_handler_ == NULL 
      // which means the internal base can not establish 
      // so there is no way but to terminate the process now
      assert( file_handler_ != NULL );
      if( file_handler_ == NULL )
        FatalAppOutput(XSYSLOG_INTERNAL_ERROR_HEADER _xT(" Can Not Create System Log!"));
      Speak(_xT("----------------------(XGE System Log)--------------------\n"));
      char_t temp_buffer[128];
      int length;
#ifdef XGE_USE_WIDE_CHAR
      length = swprintf( temp_buffer , 128 , _xT("ProcessID:%d.\n"),::GetCurrentProcessId() );
#else
      length = sprintf_s( temp_buffer , 128 , _xT("ProcessID:%d.\n"),::GetCurrentProcessId() );
#endif
      Speak(temp_buffer,length);     
      Speak(GetTimeString());
      Speak(_xT("-------------------------------------------------------\n"));
      // can not reach here
    }
    ~TinyLogFile() {
      if( file_handler_ != NULL )
        fclose( file_handler_ );
    }
    void Speak( const char_t* msg , size_t size = 0 ){
      if( size == 0 ) 
        size = 
#ifdef XGE_USE_WIDE_CHAR
        wcslen(msg);
#else
        strlen(msg);
#endif
      fwrite( msg , sizeof(char_t)*size , 1 , file_handler_ );
    }
    void CloseFile() {
      fclose( file_handler_ ) ;
      file_handler_ = NULL;
    }
    void BreakLine() {
      Speak(_xT("\n"),1);
    }
    void Flush() {
      fflush(file_handler_);
    }
  private:
    FILE* file_handler_;
  };
  size_t TinyLogFile::kTag = 0;
  class XSysLogContext : public Allocator {
  public:
    void Log( int Level , bool flush , const char_t* file , const char_t* func , const char_t* fmt , va_list list );      
    ~XSysLogContext() {
      ::DeleteCriticalSection(&critical_section_);
    }
    XSysLogContext() {
      if( ::InitializeCriticalSectionAndSpinCount(&critical_section_,2000) == FALSE ) {
        assert(0);
        FatalAppOutput(XSYSLOG_INTERNAL_ERROR_HEADER _xT("Can Not Create Critical Section!") );
      }
    }
  private:
    // In the previous system log version 
    // the system log has only one destination -- file
    // here we just add a fix to this , I separate the 4
    // level of log into 4 different file in order to
    // make things more clear 
    TinyLogFile log_file_[4];
    ::CRITICAL_SECTION critical_section_;
  };

  void XSysLogContext::Log( int level , bool flush , const char_t* file , const char_t* func , const char_t* fmt , va_list list ) {
    assert( CHECK_LOG_LEVEL(level) );   
    if( !CHECK_LOG_LEVEL(level) ) {
      return;
    }
    char_t information_buffer[ MAX_CHAR_STACK_BUFFER_SIZE ];
    char_t appending_buffer [ MAX_CHAR_STACK_BUFFER_SIZE ];
    int length = 
#ifdef XGE_USE_WIDE_CHAR
      vswprintf_s(
#else
      vsprintf_s(
#endif
      appending_buffer,
      MAX_CHAR_STACK_BUFFER_SIZE,
      fmt,
      list );
    // Overflow 
    if( length <0 || length == MAX_CHAR_STACK_BUFFER_SIZE )
      return;
    // GetTimeString Is Not Thread Safe
    // so I should lock it
    ::EnterCriticalSection(&critical_section_);
    const char_t * time_str = GetTimeString();
    ::LeaveCriticalSection(&critical_section_);
    // format the message
    length = 
#ifdef XGE_USE_WIDE_CHAR
      swprintf_s( 
#else
      sprintf_s(
#endif
      information_buffer , 
      MAX_CHAR_STACK_BUFFER_SIZE,
      _xT("-----------------------------\n%s")
      _xT("%s\n") // the log additional formatted information ret_str
      _xT("[TIME]: %s") // the time string goes here
      _xT("[FilePath||FunctionName]: %s||%s.\n") // the file path and function name
      _xT("-----------------------------\n") ,
      kLogPrefix[level],
      appending_buffer,
      time_str,
      file,func
      );
    // Overflow 
    if( length <0 || length == MAX_CHAR_STACK_BUFFER_SIZE )
      return;
    // log the message
    log_file_[level-1].Speak(information_buffer,(size_t)length);
    if( flush )
      log_file_[level-1].Flush();
    // if the level is fatal
    // we should terminate the application now
    if( level == FATAL ) {
#ifdef _DEBUG

#ifdef XGE_USE_WIDE_CHAR
      // Output the string information into the VS console
      OutputDebugStringW(
        information_buffer
        );
#else
      OutputDebugStringA(
        information_buffer
        );
#endif// XGE_USE_WIDE_CHAR
      // Give back the control flow to the VS debugger
      ::DebugBreak();
      abort() ;

#else 
      // In release version 
      // We have to tell the user now

      FatalAppOutput(
        _xT("We have encountered a fatal error in the game engine.")
        _xT("Check the information in the game engine log---XGameEngine.syslog.log")
        );
#endif // _DEBUG
    
  }
}
}

#undef XSYSLOG_MAX_FORMAT_PASS
#undef XSYSLOG_THREAD_LOCAL_BUFFER
#undef XSYSLOG_HEAP_BUFFER
#undef XSYSLOG_INTERNAL_ERROR_HEADER

}// namespace xge_internal
}//namespace xge

namespace xge{
namespace xge_internal{

  void* SystemLog::kContext = NULL;


  void SystemLog::Initialize() {
    if( kContext != NULL ) {
      // using basic assert instead of XAssert
      // this is the basic class , reducing the dependency
      assert( !"Reinitialize SystemLog!" );
      return;
    }
    kContext = ( new XSysLogContext() );
    // initialize the destroy callback routine
    AtExitManager::RegisterSystemLogCallback(Destroy);
  }

  void SystemLog::Destroy() {
    if( kContext != NULL ) {
      XSysLogContext* ctx = (XSysLogContext*)(kContext);
      delete ctx;
    }
  }

  void SystemLog::Log( int level , bool flush , const char_t* filename , const char_t* func , const char_t * fmt , ... ) {
    if( kContext == NULL ) {
      assert(!"Not initialize the system log!");
      return;
    }
    va_list list;
    va_start( list,fmt );
    ((XSysLogContext*)(kContext))->Log( level ,flush , filename , func ,fmt , list );
  }
}
}

